import * as d3 from '../../dependency/d3.v5.js'

import {
    SVG_ID, CLICK, G_ID, LINK_LAYER, NODE_LAYER, MENU_LAYER, NODE_PREFIX, NODE_TEXT_PREFIX
} from '../common/const.js'

import {addCanvasEvent, clickCanvas} from '../events/canvasEvents.js'
import {addNodeEvent} from '../events/nodeEvents'
import {zoom, zoomStart, zooming, zoomEnd} from '../events/zoom.js'
import {overNode, outNode, downNode, upNode, clickNode, dblclickNode} from '../events/nodeEvents'
import {drag, dragstarted, dragged, dragended} from '../events/drag.js'

import {drawLink, getLinkTextPosition} from './link'
import {preprocess} from '../common/preprocess.js'
import {setMenuData} from '../menu/menu.js'
import {delLinkByNode} from './utils'

import '../common/main.css'


let nodes = []
let links = []

let nodeParts, linkParts

let simulation

let containerBox = null
let containerID = ''
let width = 0
let height = 0

let svg = null
let contentbBody, nodesLayer, linksLayer

let settings = {}


const init = (dom) => {
    containerBox = dom
    width = dom.clientWidth
    height = dom.clientHeight
    containerID = dom.id
}

// 清除图谱
const clearGraph = () => {
    // 选择特定的容器并清除其内容
    d3.select('#kg').selectAll('*').remove();
    // 将 svg 设为 null
    svg = null;
}


const createLayout = () => {
    svg = d3.select('#' + containerID)
        .attr('oncontextmenu', () => 'self.event.returnValue=false')
        .append('svg')
        .attr('id', SVG_ID)
        .attr('class', SVG_ID)
        .attr('width', width)
        .attr('height', height)

    svg.on('click', clickCanvas)


    const defs = svg.append('defs'); // 定义裁剪路径的容器

    defs.append('clipPath') // 添加裁剪路径定义
        .attr('id', 'node-clip') // 给裁剪路径一个ID
        .append('circle') // 使用圆形裁剪区域
        .attr('r', '10') // 半径设置为1，后面会动态调整
        .attr('cx', '0') // 中心点X
        .attr('cy', '0'); // 中心点Y

    svg.call(zoom
        .scaleExtent([1 / 8, 4])
        .on('start', zoomStart)
        .on('zoom', zooming)
        .on('end', zoomEnd))
        .on('dblclick.zoom', null)

    contentbBody = svg.append('g')
        .attr('class', G_ID)

    contentbBody.append('g')
        .attr('id', LINK_LAYER);
    contentbBody.append('g')
        .attr('id', NODE_LAYER);
    nodesLayer = d3.select('#' + NODE_LAYER);
    linksLayer = d3.select('#' + LINK_LAYER);


}

const createSimulation = function () {
    const {linkDistance, manyBodyStrength, collideRadius} = settings

    function ticked() {
        if (nodeParts) {
            nodeParts.selectAll('circle')
                .attr('cx', d => parseFloat(d.x))
                .attr('cy', d => parseFloat(d.y));
            nodesLayer.selectAll('.image')
                .attr('x', d => d.x - d.r)
                .attr('y', d => d.y - d.r);
            nodesLayer.selectAll('text')
                .style('pointer-events', 'none')
                .attr('x', d => d.x)
                .attr('y', d => d.y);
            linksLayer.selectAll('path')
                .attr('d', d => drawLink(d))
                // .attr('d', d => selectPath(d).outline(selectPath(d).textWidth(d)))
                .attr('x1', d => d.source.x)
                .attr('y1', d => d.source.y)
                .attr('x2', d => d.target.x)
                .attr('y2', d => d.target.y);
            linksLayer.selectAll('g')
                .attr('transform', (d) => {
                    const dx = d.target.x - d.source.x;
                    const dy = d.target.y - d.source.y;
                    const angle = (((Math.atan2(dy, dx) / Math.PI) * 180) + 360) % 360;
                    d.naturalAngle = d.target.id === d.startNode ? (angle + 180) % 360 : angle;
                    return `translate(${d.source.x} ${d.source.y}) rotate(${d.naturalAngle})`;
                });
            // 给文字添加transform，达到左右旋转后文字自动改变方向的效果
            linksLayer.selectAll('text')
                .style('pointer-events', 'none')
                .attr('x', d => getLinkTextPosition(d).x)
                .attr('y', (d) => {
                    // const arc = selectPath(d);
                    const textY = getLinkTextPosition(d).y
                    const dx = d.target.x - d.source.x;
                    const dy = d.target.y - d.source.y;
                    const angle = (((Math.atan2(dy, dx) / Math.PI) * 180) + 360) % 360;
                    d.naturalAngle = d.target.id === d.startNode ? (angle + 180) % 360 : angle;
                    if (d.naturalAngle < 90 || d.naturalAngle > 270) {
                        return textY;
                        // return arc.textpos_y();
                    }
                    return textY - 60;
                    // return arc.textpos_y() - 60;
                })
                .attr('transform', (d) => {
                    // const arc = selectPath(d);
                    const textPos = getLinkTextPosition(d)
                    const dx = d.target.x - d.source.x;
                    const dy = d.target.y - d.source.y;
                    const angle = (((Math.atan2(dy, dx) / Math.PI) * 180) + 360) % 360;
                    d.naturalAngle = d.target.id === d.source.id ? (angle + 180) % 360 : angle;
                    if (d.naturalAngle < 90 || d.naturalAngle > 270) {
                        return null;
                    }
                    return `rotate(180 ${textPos.x} ${textPos.y})`;
                });
        }
    }

    simulation = d3.forceSimulation()
        .force('x', d3.forceX(width / 2).strength(0.05))
        .force('y', d3.forceY(height / 2).strength(0.05))
        .force('link', d3.forceLink().distance(linkDistance).id(d => d.id))
        .force('charge', d3.forceManyBody().strength(manyBodyStrength))
        .force('collide', d3.forceCollide(collideRadius))
        .on('tick', ticked)
}

const updateLayout = function () {
    const updateNode = nodesLayer.selectAll('g').data(nodes, d => d.id);
    updateNode.exit().remove();

    const enterNode = updateNode.enter().append('g');
    enterNode.append('circle')
        .attr('class', d => d.nodeClass)
        .attr('id', d => `${NODE_PREFIX}_${d.id}`)
        .attr('r', d => d.r)
        .style('fill', d => d.color)
        .attr('cx', d => d.x)
        .attr('cy', d => d.y);

    enterNode.append('text')
        .attr('class', d => `${NODE_TEXT_PREFIX}_${d.id}`)
        .style('fill', d => d.textColor)
        .attr('text-anchor', 'middle')
        .attr('x', d => d.x)  // 设置为节点中心
        .attr('y', d => d.y)  // 设置为节点中心
        .attr('dy', '0.35em') // 垂直偏移量，调整文本位置
        .text(d => d.name)
    //     .each(function (d) {
    //     // 创建剪切路径
    //     const clipId = `clip-${d.id}`;
    //     svg.select('defs').append('clipPath')
    //         .attr('id', clipId)
    //         .append('circle')
    //         .attr('r', d.r)
    //         .attr('cx', d.x)
    //         .attr('cy', d.y);
    //     // 应用剪切路径到文本元素
    //     d3.select(this).attr('clip-path', `url(#${clipId})`)
    //         .attr('x', d => d.x)  // 设置为节点中心
    //         .attr('y', d => d.y); // 设置为节点中心
    // });
    // 鼠标悬浮展示的文字
    enterNode.append('title').text(d => d.names);

    nodeParts = enterNode.merge(updateNode);

    nodeParts.selectAll('circle')
        .attr('cx', d => d.x)
        .attr('cy', d => d.y);

    nodeParts.selectAll('text')
        .attr('x', d => d.x)
        .attr('y', d => d.y);


    nodeParts
        .attr('xlink:href', d => d.cover)
        .attr('class', d => 'image ' + d.coverClass)
        .attr('r', d => d.r)
        .attr('width', d => 2 * d.r)
        .attr('height', d => 2 * d.r)
        .attr('x', (d) => (!d.x || isNaN(d.x)) ? 0 : d.x - d.r)
        .attr('y', (d) => (!d.y || isNaN(d.y)) ? 0 : d.y - d.r)
        .on('mouseover', overNode)
        .on('mouseout', outNode)
        .on('mousedown', downNode)
        .on('mouseup', upNode)
        .on('click', clickNode)
        .on('dblclick', dblclickNode)
        .call(drag
            .on('start', dragstarted)
            .on('drag', dragged)
            .on('end', dragended))
    const updateLink = linksLayer.selectAll('g').data(links, d => d.id + d.index)
    updateLink.exit().remove()
    linkParts = updateLink.enter().append('g');
    linkParts.append('path')
        .attr('class', d => d.linkClass)
        .attr('p-i', d => d.pathIndex)
        .style('stroke', d => d.color)
        .style('stroke-width', d => d.width)
        .style('fill', d => d.color)

    linkParts
        .append('text')
        .attr('class', d => d.textClass)
        .attr('id', d => `linkText_${d.id}`)
        .style('fill', d => d.textColor)
        .attr('text-anchor', 'middle')
        .attr('x', d => d.x)
        .attr('y', d => d.y)
        .attr('dx', () => 0)
        .attr('font-size', '10px')
        .attr('dy', '3.2em')
        .on('mouseover', d => {
            d3.event.stopPropagation();
        })
        .text(d => d.type);
    linkParts = linkParts.merge(updateLink);

    simulation.nodes(nodes);
    simulation.force('link').links(links);
    simulation.alpha(1).alphaMin(0.05).restart();
}

const addNodes = function (newNodes, newLinks) {
    newNodes.forEach((e) => {
        if (!nodes.find(z => z.id === e.id)) {
            nodes.push(e)
        }
    })
    let linkIdCounter = links.length;
    newLinks.forEach((e) => {
        if (!e.id) {
            e.id = `link-${linkIdCounter++}`; // You can replace this with a more robust ID generation method
        }
        if (!links.find(z => z.id === e.id)) {
            links.push(e)
        }
    })
    updateLayout();
}

const removeNode = function (nodeId) {
    d3.selectAll('#' + MENU_LAYER).remove()
    nodes.find((val, ind, arr) => {
        if (val.id === nodeId) {
            arr.splice(ind, 1)
            return true
        }
        return false
    });

    delLinkByNode(links, nodeId)
    updateLayout()
}

const start = function (startNodes = nodes, startLinks = links) {
    nodes.splice(0)
    links.splice(0)
    nodes.push(...startNodes)
    links.push(...startLinks)
    if (svg === null) {
        createLayout()
        createSimulation()
    }
    updateLayout()
}

const setOption = function (opt) {
    settings = Object.assign({
        'linkDistance': 170,
        'manyBodyStrength': -100,
        'collideRadius': 30,
        'nodes': [],
        'links': [],
        'dragLock': false,
        'menu': {},
        'events': {}
    }, opt)
    const {_nodes, _links, _menu} = preprocess(settings.nodes, settings.links, settings.menu)

    for (const [k, v] of Object.entries(_menu)) {
        setMenuData(k, v)
    }

    for (const [k, v] of Object.entries(settings.events)) {
        if (k === 'canvas') {
            for (const i of v) {
                if (i.type === 'click') {
                    addCanvasEvent(CLICK, e => i.method(e))
                }
            }
        } else if (k === 'node') {
            for (const i of v) {
                if (i.type === 'click') {
                    addNodeEvent(CLICK, e => i.method(e))
                }
            }
        }
    }
    start(_nodes, _links)
}
export {d3}

export {simulation}

export {contentbBody, svg, linksLayer, nodeParts, nodesLayer, settings}

export {clearGraph, init, setOption, addNodes, removeNode}
